package org.freespace.service.impl;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.freespace.Constant;
import org.freespace.api.ExceptionCode;
import org.freespace.api.JmeterDriver;
import org.freespace.config.GlobalConf;
import org.freespace.exception.CommonException;
import org.freespace.mapper.JobMapper;
import org.freespace.model.*;
import org.freespace.service.*;
import org.freespace.utils.*;
import org.freespace.utils.enums.JobStatus;
import org.freespace.utils.enums.MachineStatus;
import org.freespace.utils.response.ResultCode;
import org.freespace.vo.JobVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.net.InetAddress;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 作业表 服务实现类
 * </p>
 *
 * @author liuqi
 * @since 2023-05-08
 */
@Slf4j
@Service("jobService")
public class JobServiceImpl extends ServiceImpl<JobMapper, Job> implements IJobService {

    @Autowired
    ITestplanService testplanService;

    @Autowired
    IMachineService machineService;

    @Autowired
    IJmxScriptService scriptService;

    @Autowired
    IJmxScriptPluginService scriptPluginService;

    @Autowired
    IJmxParamfileService paramfileService;

    @Autowired
    JmeterDriver jmeterDriver;

    @Autowired
    IJobMachineService jobMachineService;

    @Autowired
    IJobDetailService jobDetailService;

    @Autowired
    IJobMachineCallbackService jobMachineCallbackService;

    @Autowired
    IEnvironmentService environmentService;

    @Autowired
    IMonitorRegisterSerivce monitorRegisterSerivce;


    @Autowired
    InfluxDBUtils influxDBUtils;

    String newline = "\n";

    @Override
    public void createJob(Job job) throws Exception {
        StringBuffer logBuff = new StringBuffer();
        Testplan testplan = testplanService.getById(job.getTestplanId());

        logBuff.append(String.format("开始压测，测试计划ID:%s 测试计划名称:%s ", testplan.getId(), testplan.getName())).append(newline);
        //创建状态为“待执行”的job
        job.setStatus(JobStatus.WAIT.getType());
        job.setCreateBy(SessionUtil.getCurrentUser());
        job.setLastUpdateBy(SessionUtil.getCurrentUser());
        job.setMasterIp(InetAddress.getLocalHost().getHostAddress());
        job.setEnvId(testplan.getEnvId());
        job.setIps(String.join(",", environmentService.getEnvMachineIps(testplan.getEnvId())));
        save(job);
        logBuff.append(String.format("创建一个待执行的作业  作业ID:%s", job.getId())).append(newline);

        try {
            executeJob(testplan, job, logBuff);
        } catch (Exception e) {
            logBuff.append(String.format("压测失败，失败原因：%s", ExceptionUtils.getStackTrace(e))).append(newline);
            log.error(ExceptionUtils.getStackTrace(e));

            //更新作业状态为失败
            job.setStatus(JobStatus.FAIL.getType());
            saveOrUpdate(job);
            logBuff.append("更新作业状态为失败").append(newline);

            //注销监控
            try {
                monitorRegisterSerivce.jobDeregister(job.getId());
            } catch (Exception ew) {
                log.error(ew.getMessage());
            }

            //删除插件，重启压力机
            try {
                List<JobMachineCallback> allCallBackMachines = jobMachineCallbackService.list(Wrappers.<JobMachineCallback>lambdaQuery().eq(JobMachineCallback::getJobId, job.getId()));
                testEndProcess(allCallBackMachines, job);
            } catch (Exception es) {
                log.error(es.getMessage());
            }
        } finally {
            job.setLogMsg(logBuff.toString());
            saveOrUpdate(job);
        }
    }

    public void executeJob(Testplan testplan, Job job, StringBuffer logBuff) {
        Integer jobId = job.getId();
        log.info("testplan:{}.jobId:{}", testplan, jobId);

        logBuff.append("作业执行准备工作开始").append(newline);
        logBuff.append("开始锁定压力机").append(newline);

        //锁定闲置压力机
        List<Machine> machines = lockMachine(jobId, testplan.getMachineNum());
        log.info("lockMachine:{}", machines);
        logBuff.append(String.format("锁定压力机完成，锁定的压力机为%s", machines)).append(newline);

        //开启监控
        logBuff.append("开始开启监控").append(newline);
        monitorRegisterSerivce.jobRegister(jobId);
        logBuff.append("开启监控完成").append(newline);

        logBuff.append("开始将作业状态改为执行中").append(newline);
        //更新job状态为执行中
//        Job job = getById(jobId);
        job.setStatus(JobStatus.DOING.getType());
        job.setJmxId(testplan.getJmxId());
        updateById(job);
        log.info("updateJobToDoing:{}", job);
        logBuff.append("将作业状态改为执行中完成").append(newline);

        //todo 测试压测机器是否连接正常

        // todo 测试脚本文件、参数文件、拆分参数文件是否正常

        logBuff.append("开始往压测机器推送脚本文件、插件文件、参数文件").append(newline);
        //往压测机器推送脚本文件、插件文件、参数文件
        JmxScript scriptFile = scriptService.getById(testplan.getJmxId());
        List<JmxPlugin> scriptPlugins = scriptPluginService.getScriptPluginsByScriptId(testplan.getJmxId());
        List<JmxParamfile> paramfiles = paramfileService.getParamfilesByScriptId(testplan.getJmxId());
        log.info("scriptFile:{} scriptPlugins:{} paramfiles:{}", scriptFile, scriptPlugins, paramfiles);
        for (Machine machine : machines) {
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("job", job);
            dataMap.put("fileServerUrl", Constant.fileServerUrl);
            dataMap.put("scriptFile", scriptFile);
            dataMap.put("pluginFiles", scriptPlugins.parallelStream().map(jmxPlugin -> jmxPlugin.getPath()).collect(Collectors.toList()));
            dataMap.put("paramFiles", paramfiles.parallelStream().map(paramfile -> paramfile.getPath()).collect(Collectors.toList()));

            String url = String.format("http://%s:%s/proxy/job/download", machine.getIp(), "7777");
            if ("dev".equalsIgnoreCase(SpringBeanUtil.getActiveProfile())) {
                url = "http://localhost:7777/proxy/job/download";
            }
            log.info("url:{} dataMap:{}", url, dataMap);
            HttpRequest httpRequest = HttpUtil.createPost(url).body(JSONUtil.toJsonStr(dataMap));
            String resBody = httpRequest.execute().body();
            log.info("resBody:{}", resBody);
            JSONObject jo = JSONUtil.parseObj(resBody);
            String code = jo.get("code").toString();
            log.info(resBody);
            String message = jo.get("message").toString();
            if (ResultCode.SUCCESS.getCode().equals(code)) {

            }

        }
        logBuff.append("往压测机器推送脚本文件、插件文件、参数文件完成").append(newline);

        logBuff.append("开始往所有压力机jmeter发起压测请求").append(newline);
        log.info("start jmeter performance test");
        for (Machine machine : machines) {
            logBuff.append(String.format("开始往%s发起压测请求", machine.getIp())).append(newline);
            String jmxLocalDir = GlobalConf.getConfig("JmeterHome") + File.separator + "jmx" + File.separator + testplan.getId() + File.separator + jobId;
            log.info("jmxLocalDir:{}", jmxLocalDir);
            String jmxLocalFile = jmxLocalDir + File.separator + scriptFile.getPath().substring(scriptFile.getPath().lastIndexOf("/") + 1);
            log.info("jmxLocalFile:{}", jmxLocalFile);
            Map<String, String> dataMap = new HashMap<>();
            dataMap.put("fullJmxFileName", jmxLocalFile);
            dataMap.put("jobId", jobId + "");
            dataMap.put("stressIp", machine.getIp());
            dataMap.put("influxdb.url", "http://192.168.30.10:8086/write?db");
            dataMap.put("influxdb.db", "jmeter_db");
            dataMap.put("callbackUrl", "http://192.168.30.10:8080/jmeter/job/finish");
            log.info("testData:{}", dataMap);
            jmeterDriver.start(dataMap, machine.getIp());
            logBuff.append(String.format("请求参数：%s", dataMap)).append(newline);
            logBuff.append(String.format("往%s发起压测请求完成", machine.getIp())).append(newline);
        }
        logBuff.append("往所有压力机jmeter发起压测请求完成").append(newline);
    }

    @Override
    public List<Job> getWaitJob() {
        QueryWrapper<Job> wrapper = new QueryWrapper<>();
        wrapper.eq("status", JobStatus.WAIT.getType());
        return list(wrapper);
    }

    @Override
    public void finishJob(String jobId, String ip, String endTime, String status) {
        try {

            log.info("jobId:{} ip:{} endTime:{} status:{}", jobId, ip, endTime, status);
            synchronized (this) {
                JobMachineCallback jobMachineCallback = new JobMachineCallback();
                jobMachineCallback.setJobId(Integer.valueOf(jobId));
                jobMachineCallback.setIp(ip);
                jobMachineCallback.setEndTime(DateUtil.getDateTimeOfTimestamp(Long.valueOf(endTime)));
                jobMachineCallbackService.save(jobMachineCallback);
            }

            //查询job所有关联的机器，查看是否都已经回调
            List<JobMachine> allLockMachines = jobMachineService.list(Wrappers.<JobMachine>lambdaQuery().eq(JobMachine::getJobId, jobId));
            log.info("jobId:{} allLockMachines:{}", jobId, allLockMachines);
            List<JobMachineCallback> allCallBackMachines = jobMachineCallbackService.list(Wrappers.<JobMachineCallback>lambdaQuery().eq(JobMachineCallback::getJobId, jobId));
            log.info("jobId:{} allCallBackMachines:{}", jobId, allCallBackMachines);

            Job job = getById(jobId);
            if (allLockMachines.size() == allCallBackMachines.size()) {
                try {
                    log.info("jobId:{} allMachinesCallBack", jobId);
                    //所有压力机都压测结束并回调，更新作业状态为完成
                    job.setEndTime(allCallBackMachines.parallelStream().map(m -> m.getEndTime()).max(Comparator.comparing(i -> i)).get());
                    if ("error".equals(status)) {
                        job.setStatus(JobStatus.FAIL.getType());
                    } else {
                        job.setStatus(JobStatus.FINISHED.getType());
                    }
                    job.setLastUpdateTime(LocalDateTime.now());
                    updateById(job);
                    log.info("jobId:{} updateToFinished", jobId);

                    //将压测结果数据入库到detail表
                    List<Map<String, String>> perfomances = jobDetailService.apiPerfList(job.getTestplanId(), job.getId());
                    log.info("jobId:{} perfomances", perfomances);
                    for (Map<String, String> perfomance : perfomances) {
                        JobDetail jobDetail = new JobDetail();
                        jobDetail.setTestplanId(job.getTestplanId());
                        jobDetail.setJobId(job.getId());
                        jobDetail.setThreadNum(Integer.valueOf(perfomance.get("num_of_thread")));
                        jobDetail.setTps(perfomance.get("tps"));
                        jobDetail.setMaxTps(perfomance.get("maxTps"));
                        jobDetail.setLabel(perfomance.get("label"));
                        //todo median
//                    jobDetail.setMedian(perfomance.get("median"));
                        jobDetail.setMax(perfomance.get("max"));
                        jobDetail.setMin(perfomance.get("min"));
                        jobDetail.setError(perfomance.get("error"));
                        jobDetail.setAvg(perfomance.get("avg"));
                        jobDetail.setErrorPercent(perfomance.get("errorPercent"));
                        jobDetail.setSamples(perfomance.get("samples"));
                        jobDetail.setDuration(perfomance.get("duration"));
                        jobDetail.setLine90(perfomance.get("line90"));
                        jobDetail.setLine95(perfomance.get("line95"));
                        jobDetail.setLine99(perfomance.get("line99"));
                        jobDetail.setTps(perfomance.get("tps"));
//                    jobDetail.setReceived(perfomance.get("received"));
//                    jobDetail.setSent(perfomance.get("sent"));
                        jobDetail.setCreateTime(LocalDateTime.now());
                        jobDetailService.save(jobDetail);
                        log.info("jobId:{} save detail:{}", jobDetail);
                    }
                } catch (Exception e) {
                    log.error(ExceptionUtil.stacktraceToString(e));
                    // todo 後面刪除這個trycatch
                }

                testEndProcess(allCallBackMachines, job);
            }
            log.info("jobId.end:{}", jobId);
        } finally {
            monitorRegisterSerivce.jobDeregister(Integer.parseInt(jobId));
        }
    }

    private void testEndProcess(List<JobMachineCallback> allCallBackMachines, Job job) {
        for (JobMachineCallback machine : allCallBackMachines) {
            //删除插件
            String url = String.format("http://%s:%s/proxy/job/deletePluginFile", machine.getIp(), "7777");
            if ("dev".equalsIgnoreCase(SpringBeanUtil.getActiveProfile())) {
                url = "http://localhost:7777/proxy/job/deletePluginFile";
            }

            Map<String, List<String>> dataMap = new HashMap<>();
            dataMap.put("pluginList", scriptPluginService.getScriptPluginsByScriptId(job.getJmxId()).stream().map(plugin -> plugin.getPath().substring(plugin.getPath().lastIndexOf("/"))).collect(Collectors.toList()));
            log.info("jobId:{} url:{} removePlugins:{}", job.getId(), url, dataMap.get("pluginList"));
            HttpRequest httpRequest = HttpUtil.createPost(url).body(JSONUtil.toJsonStr(dataMap));
            String resBody = httpRequest.execute().body();
            log.info("jobId:{} removePlugins.resBody:{}", job.getId(), resBody);
            JSONObject jo = JSONUtil.parseObj(resBody);
            String code = jo.get("code").toString();
            log.info(resBody);
            String message = jo.get("message").toString();
            if (ResultCode.SUCCESS.getCode().equals(code)) {

            }
        }

        for (JobMachineCallback machine : allCallBackMachines) {
            //重启压力机
            String url = String.format("http://%s:%s/proxy/machine/restart", machine.getIp(), "7777");
            if ("dev".equalsIgnoreCase(SpringBeanUtil.getActiveProfile())) {
                url = "http://localhost:7777/proxy/machine/restart";
            }

            Map<String, String> dataMap = new HashMap<>();
            dataMap.put("callback", String.format(Constant.callbackUrl, IpUtil.getIpAddress(), "8080"));
            log.info("jobId:{} url:{} restart:{}", job.getId(), url, dataMap.get("callback"));
            HttpRequest httpRequest = HttpUtil.createPost(url).body(JSONUtil.toJsonStr(dataMap));
            String resBody = httpRequest.execute().body();
            log.info("resBody:{}", resBody);
            JSONObject jo = JSONUtil.parseObj(resBody);
            String code = jo.get("code").toString();
            log.info(resBody);
            String message = jo.get("message").toString();
            if (ResultCode.SUCCESS.getCode().equals(code)) {

            }
        }
    }

    public List<Machine> lockMachine(int jobId, int needMachineNum) {
        //查询闲置的压力数量
        List<Machine> idleMachines = machineService.getIdleMachine();
        if (idleMachines.size() < needMachineNum) {
            log.info("idleMachines.size() < needMachineNum");
            throw new CommonException(ExceptionCode.MACHINE_NUM_NOT_ENOUGH);
        }


        Collections.shuffle(idleMachines);

        List<Machine> needMachineList = idleMachines.parallelStream().limit(needMachineNum).collect(Collectors.toList());

        if (needMachineList.size() == 0) {
            log.info("needMachineList.size() == 0");
            throw new CommonException(ExceptionCode.MACHINE_NUM_NOT_ENOUGH);
        }

        for (Machine machine : needMachineList) {
            //修改压力机状态为执行中
            machine.setLastUpdateBy(SessionUtil.getCurrentUser());
            machine.setLastUpdateTime(LocalDateTime.now());
            machine.setStatus(MachineStatus.DOING.getType());
            machineService.updateById(machine);
        }

        //关联压力机到job
        for (Machine machine : needMachineList) {
            JobMachine jobMachine = new JobMachine();
            jobMachine.setJobId(jobId);
            jobMachine.setMachineId(machine.getId());
            jobMachineService.save(jobMachine);
        }

        return needMachineList;
    }

    @Override
    public Map<String, Object> getTpsData(JobVO vo) {
        List<String> dataArray = new ArrayList<>();
        Map<String, Object> result = new HashMap<>();
        Job job = getById(vo.getJobId());
        List<Map<String, Object>> reportList = new ArrayList<>();
        if ("ok".equals(vo.getModel())) {
            reportList = influxDBUtils.queryOkTpsReportList(vo.getJobId(), vo.getLabel());
        } else {
            reportList = influxDBUtils.queryTpsReportList(vo.getJobId(), vo.getLabel());
        }
        //按timestamp字段排序
        result = sortAndFormatData(dataArray, result, reportList);
        return result;
    }


    @Override
    public Map<String, Object> getResponseTimeData(JobVO vo) {
        List<String> dataArray = new ArrayList<>();
        Map<String, Object> result = new HashMap<>();
        List<Map<String, Object>> reportList = new ArrayList<>();
        if ("ok".equals(vo.getModel())) {
            reportList = influxDBUtils.queryOkResponseTimeReportList(vo.getJobId(), vo.getLabel());
        } else {
            reportList = influxDBUtils.queryResponseTimeReportList(vo.getJobId(), vo.getLabel());
        }
        //按timestamp字段排序
        result = sortAndFormatData(dataArray, result, reportList);
        return result;
    }

    @Override
    public Map<String, Object> getMaxResponseTimeData(JobVO vo) {
        List<String> dataArray = new ArrayList<>();
        Map<String, Object> result = new HashMap<>();
        List<Map<String, Object>> reportList = new ArrayList<>();
        if ("ok".equals(vo.getModel())) {
            reportList = influxDBUtils.queryOKMaxResponseTimeReportList(vo.getJobId(), vo.getLabel());
        } else {
            reportList = influxDBUtils.queryMaxResponseTimeReportList(vo.getJobId(), vo.getLabel());
        }
        //按timestamp字段排序
        result = sortAndFormatData(dataArray, result, reportList);
        return result;
    }

    @Override
    public Map<String, Object> getline90Data(JobVO vo) {
        List<String> dataArray = new ArrayList<>();
        Map<String, Object> result = new HashMap<>();
        List<Map<String, Object>> reportList = new ArrayList<>();
        if ("ok".equals(vo.getModel())) {
            reportList = influxDBUtils.queryOkLine90Data(vo.getJobId(), vo.getLabel());
        } else {
            reportList = influxDBUtils.queryLine90Data(vo.getJobId(), vo.getLabel());
        }
        //按timestamp字段排序
        result = sortAndFormatData(dataArray, result, reportList);
        return result;
    }

    @Override
    public Map<String, Object> getline95Data(JobVO vo) {
        List<String> dataArray = new ArrayList<>();
        Map<String, Object> result = new HashMap<>();
        List<Map<String, Object>> reportList = new ArrayList<>();
        if ("ok".equals(vo.getModel())) {
            reportList = influxDBUtils.queryOkLine95Data(vo.getJobId(), vo.getLabel());
        } else {
            reportList = influxDBUtils.queryLine95Data(vo.getJobId(), vo.getLabel());
        }
        //按timestamp字段排序
        result = sortAndFormatData(dataArray, result, reportList);
        return result;
    }

    @Override
    public Map<String, Object> getline99Data(JobVO vo) {
        List<String> dataArray = new ArrayList<>();
        Map<String, Object> result = new HashMap<>();
        List<Map<String, Object>> reportList = new ArrayList<>();
        if ("ok".equals(vo.getModel())) {
            reportList = influxDBUtils.queryOkLine99Data(vo.getJobId(), vo.getLabel());
        } else {
            reportList = influxDBUtils.queryLine99Data(vo.getJobId(), vo.getLabel());
        }
        //按timestamp字段排序
        result = sortAndFormatData(dataArray, result, reportList);
        return result;
    }

    private Map<String, Object> sortAndFormatData(List<String> dataArray, Map<String, Object> result, List<Map<String, Object>> reportList) {
        Collections.sort(reportList, new Comparator<Map<String, Object>>() {
            @Override
            public int compare(Map<String, Object> o1, Map<String, Object> o2) {
                String ts1 = (String) o1.get("timestamp");
                long timestamp1 = DateUtil.utcToTs(ts1);

                String ts2 = (String) o2.get("timestamp");
                long timestamp2 = DateUtil.utcToTs(ts2);

                long diff = timestamp1 - timestamp2;
                if (diff > 0) {
                    return 1;
                } else if (diff < 0) {
                    return -1;
                }
                return 0;
            }
        });

        if (!reportList.isEmpty()) {
            result = new HashMap<>();
            Set<String> sets = new HashSet<>();
            List<Double> tps = new ArrayList<>();
            for (Map<String, Object> map : reportList) {
                String ts = (String) map.get("timestamp");
                long timestamp = DateUtil.utcToTs(ts);
                String date = DateUtil.timestampToDateString(timestamp, DateUtil.yyyyMMdd);
                sets.add(date);
                dataArray.add(DateUtil.timestampToHourDateString(timestamp));
                Double value = (Double) map.get("value");
                tps.add(value == null ? 0 : value.doubleValue());
            }
            result.put("dataMap", tps);
            result.put("dataArray", dataArray);
            result.put("dataTitle", sets);
        }
        return result;
    }

    @Override
    public void stop(Integer jobId) {
        Job job = getById(jobId);
        job.setStatus(JobStatus.STOP.getType());
        job.setLastUpdateTime(LocalDateTime.now());
        job.setLastUpdateBy(SessionUtil.getCurrentUser());
        updateById(job);

        //注销监控
        try {
            monitorRegisterSerivce.jobDeregister(jobId);
        } catch (Exception e) {
            log.error(e.getMessage());
        }

        //删除插件，重启压力机
        try {
            List<JobMachineCallback> allCallBackMachines = jobMachineCallbackService.list(Wrappers.<JobMachineCallback>lambdaQuery().eq(JobMachineCallback::getJobId, jobId));
            testEndProcess(allCallBackMachines, job);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

    @Override
    public Object getPlatmforLog(Integer jobId) {
        String logMsg = getById(jobId).getLogMsg();
        return logMsg;
    }

    @Override
    public Set<String> getIpList(Integer jobId) {
        List<JobMachine> list = jobMachineService.list(Wrappers.<JobMachine>lambdaQuery().eq(JobMachine::getJobId, jobId));
        Set<String> ipList = new HashSet<>();
        for (JobMachine jobMachine : list) {
            Machine machine = machineService.getById(jobMachine.getMachineId());
            if (machine != null) {
                ipList.add(machine.getIp());
            }
        }
        return ipList;
    }
}
